package com.tchepannou.limbe.validation;

import com.tchepannou.limbe.validation.constraints.ValidEmail;
import com.tchepannou.util.StringUtil;

import javax.validation.Constraint;
import javax.validation.ValidationException;
import javax.validation.constraints.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * User: herve
 * Date: 14-01-12 12:58 PM
 */
public class ValidatorEngine
{
    //-- Attribute
    private static final String DEFAULT_PACKAGE = "javax.validation.constraints";

    private static final ValidatorEngine INSTANCE = new ValidatorEngine();
    private Map<Class, Validator> _validators = new HashMap<Class, Validator>();


    //-- Constructor
    private ValidatorEngine ()
    {
        register(AssertFalse.class, new AssertTrueValidator());
        register(AssertTrue.class, new AssertTrueValidator());
        register(DecimalMax.class, new DecimalMaxValidator());
        register(DecimalMin.class, new DecimalMinValidator());
        register(Future.class, new FutureValidator());
        register(Max.class, new MaxValidator());
        register(Min.class, new MinValidator());
        register(NotNull.class, new NotNullValidator());
        register(Null.class, new NullValidator());
        register(Past.class, new PastValidator());
        register(Size.class, new SizeValidator());
        register(Pattern.class, new PatternValidator());
        register(ValidEmail.class, new ValidEmailValidator());
    }

    //-- Public
    public static ValidatorEngine getInstance ()
    {
        return INSTANCE;
    }

    public void register (Class type, Validator validator)
    {
        _validators.put(type, validator);
    }

    public Validator getValidator (Class type)
    {
        Validator validator = _validators.get(type);
        if (validator == null)
        {
            throw new IllegalStateException("Not Supported: " + type);
        }
        return validator;
    }

    public void validate (Object bean)
    {
        List<String> failures = new ArrayList<String>();

        /* Validate the bean */
        for (Annotation annotation : bean.getClass().getAnnotations())
        {
            if (isConstraint(annotation))
            {
                Class type = annotation.annotationType();
                Validator validator = getValidator(type);
                if (!validator.validate(bean))
                {
                    failures.add(type.getSimpleName() + ": " + bean);
                }
            }
        }

        /* Validator the field */
        for (Field field : bean.getClass().getFields())
        {
            for (Annotation annotation : field.getAnnotations())
            {
                if (isConstraint(annotation))
                {
                    Class type = annotation.annotationType();
                    Validator validator = getValidator(type);
                    if (!validator.validate(bean, field))
                    {
                        failures.add(type.getSimpleName() + ": " + bean + "->" + field.getName());
                    }
                }
            }
        }

        if (!failures.isEmpty())
        {
            String msg = StringUtil.merge(failures, "\n");
            throw new ValidationException(msg);
        }
    }

    private boolean isConstraint (Annotation annotation)
    {
        Class type = annotation.annotationType();
        Annotation anno = type.getAnnotation(Constraint.class);
        return anno != null || type.getPackage().getName().equals(DEFAULT_PACKAGE);
    }
}
